/**   
 * Copyright 2011 COMERE Harold
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package etrl.shaders;

import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.ArrayList;
import java.util.Iterator;

import com.google.gwt.user.client.Window;
import com.googlecode.gwtgl.binding.WebGLRenderingContext;
import com.googlecode.gwtgl.binding.WebGLProgram;
import com.googlecode.gwtgl.binding.WebGLShader;
import com.googlecode.gwtgl.binding.WebGLUniformLocation;

import etrl.core.Engine;
import etrl.core.RenderVisitor;
import etrl.vecmath.Matrix3f;
import etrl.vecmath.Matrix4f;

/**
 * \class DynamicShaderProgram
 * \brief Class allowing to build dynamically a shader program
 * @author harold
 *
 */
public class DynamicShaderProgram
{
	/**
	 * \enum Binding
	 * \brief Built-in bindings to bind scene graph data with random glsl variables
	 * @author harold
	 *
	 */
	public enum Binding
	{
		BINDING_NONE,				/** no uniform binding defined **/
		BINDING_MODELVIEW_MATRIX,	/** bind with the model view matrix uniform **/
		BINDING_PROJECTION_MATRIX,	/** bind with the projection matrix uniform **/
		BINDING_NORMAL_MATRIX,		/** bind with the normal matrix uniform **/
		BINDING_TEXTURE_MATRIX0,	/** bind with the texture matrix uniform of texture unit 0 **/
		BINDING_TEXTURE_MATRIX1,	/** bind with the texture matrix uniform of texture unit 1 **/
		BINDING_TEXTURE_MATRIX2,	/** bind with the texture matrix uniform of texture unit 2 **/
		BINDING_TEXTURE_MATRIX3,	/** bind with the texture matrix uniform of texture unit 3 **/
		BINDING_TEXTURE_MATRIX4,	/** bind with the texture matrix uniform of texture unit 4 **/
		BINDING_TEXTURE_MATRIX5,	/** bind with the texture matrix uniform of texture unit 5 **/
		BINDING_TEXTURE_MATRIX6,	/** bind with the texture matrix uniform of texture unit 6 **/
		BINDING_TEXTURE_MATRIX7,	/** bind with the texture matrix uniform of texture unit 7 **/
		BINDING_TEXTURE_COUNT,		/** bind with the texture count uniform **/
		BINDING_TEXTURE0,			/** bind with the sampler2d uniform on texture unit 0 **/
		BINDING_TEXTURE1,			/** bind with the sampler2d uniform on texture unit 1 **/
		BINDING_TEXTURE2,			/** bind with the sampler2d uniform on texture unit 2 **/
		BINDING_TEXTURE3,			/** bind with the sampler2d uniform on texture unit 3 **/
		BINDING_TEXTURE4,			/** bind with the sampler2d uniform on texture unit 4 **/
		BINDING_TEXTURE5,			/** bind with the sampler2d uniform on texture unit 5 **/
		BINDING_TEXTURE6,			/** bind with the sampler2d uniform on texture unit 6 **/
		BINDING_TEXTURE7,			/** bind with the sampler2d uniform on texture unit 7 **/
		BINDING_LIGHT_DATA,			/** bind with the light data array uniform **/
		BINDING_LIGHT_COLOR,		/** bind with the light color array uniform **/
		BINDING_LIGHT_COUNT			/** bind with the light count uniform **/
	}
	
	/**
	 * \brief Maximum number of lights supported by shaders.
	 * As the glsl array size must be a constant, we have to fix it
	 */
	public final int MAXIMUM_NUMBER_OF_LIGHTS = 8;
	
	/**
	 * Binding map between a built in binding and a variable name
	 */
	private TreeMap <Binding, String> _bindings;
	
	/**
	 * The built vertex shader source
	 */
	private String _vertexShaderSource;
		
	/**
	 * The built fragment shader source
	 */
	private String _fragmentShaderSource;
	
	/**
	 * Map storing all registeres variables and their name
	 */
	private TreeMap<String, Variable> _variableMap;
	
	/**
	 * Vector of registered variables to keep trace of registering order which is important
	 */
	private ArrayList<Variable> _variableVect;
	
	/**
	 * Map storing all registered variable modifiers and their name
	 */
	private TreeMap<String, VariableModifier> _variableModifierMap;
	
	/**
	 * Vector of registered variable modifiers to keep trace of registering order which is important
	 */
	private ArrayList<VariableModifier> _variableModifierVect;

	/**
	 * The GL context
	 */
	private WebGLRenderingContext _gl;
	
	/**
	 * The built GL shader program object
	 */
	private WebGLProgram _program;
		
	/**
	 * Flag determining if the shader program is valid or not
	 */
	private boolean _programIsValid;

	/**
	 * Map storing the uniform location of program's uniform variable to have
	 * a faster access to them than calling the webgl getUniformLocation
	 */
	private TreeMap <String, WebGLUniformLocation> _uniformLocations;
	
	/**
	 * \brief Initializes the dynamic shader program
	 */
	protected DynamicShaderProgram()
	{
		_uniformLocations = new TreeMap <String, WebGLUniformLocation>();
		_programIsValid = false;

		_bindings = new TreeMap <Binding, String>();
	
		_variableMap = new TreeMap<String, Variable>();
		_variableVect = new ArrayList<Variable>();
		_variableModifierMap = new TreeMap<String, VariableModifier>();
		_variableModifierVect = new ArrayList<VariableModifier>();

		_gl = Engine.getInstance().getGL();

		// Add the mandatory position VertexAttributeVariable needed for vertex
		// shader built-in output.
		// Its forward mode is VERTEX_ONLY because it is only used for the
		// built-in output... If the user wants
		// to use it within other shader, he has to declare a CustomVariable
		// with a VariableModifier on it to get
		// the "position" vertex attribute value.
		addVertexAttributeVariable("va_Position", 	new GLSLType(GLSLType.Type.VEC4), VariableForwardMode.VERTEX_ONLY);

		// Add too other vertex attributes used while rendering Drawables
		// Color, normal and texcoord must be forwarded to fragment shader too, unlike the position attribute which is passed to opengl via gl_Position
		addVertexAttributeVariable("va_Color", 		new GLSLType(GLSLType.Type.VEC4), VariableForwardMode.VERTEX_AND_FRAGMENT);
		addVertexAttributeVariable("va_Normal", 	new GLSLType(GLSLType.Type.VEC3), VariableForwardMode.VERTEX_AND_FRAGMENT);
		
		// About tex coord, the vertex attribute must be multiplied by the built-in texture matrix and be forwarded
		// to fragment shader using a custom variable
		addVertexAttributeVariable("va_TexCoord", 	new GLSLType(GLSLType.Type.VEC2), VariableForwardMode.VERTEX_ONLY);
		
		// Add the mandatory custom_color CutstomVariable needed for fragment
		// shader output.
		// Its forward mode is FRAGMENT_ONLY because it is only used for the
		// output... If the user wants
		// to modify it, he has to define VariableModifiers on it.
		addCustomVariable("biu_CustomColor", new GLSLType(GLSLType.Type.VEC4), VariableForwardMode.FRAGMENT_ONLY, 1);
	}

	/**
	 * \brief Builds the vertex shader source
	 */
	private void buildVertexShaderSource()
	{
		_vertexShaderSource = new String("");

		for (Iterator<Variable> it = _variableVect.iterator(); it.hasNext();)
		{
			Variable var = it.next();
			_vertexShaderSource += var.getDeclarationInVertexShader();
		}

		_vertexShaderSource += "\n";

		for (Iterator<VariableModifier> it = _variableModifierVect.iterator(); it.hasNext();)
		{
			VariableModifier modifier = it.next();
			_vertexShaderSource += modifier.getFunction(ShaderStage.VERTEX);
		}

		_vertexShaderSource += "void main()\n{\n";

		for (Iterator<Variable> it = _variableVect.iterator(); it.hasNext();)
		{
			Variable var = it.next();
			_vertexShaderSource += var.getInitialization();
		}

		_vertexShaderSource += "\n";

		for (Iterator<VariableModifier> it = _variableModifierVect.iterator(); it.hasNext();)
		{
			VariableModifier modifier = it.next();
			_vertexShaderSource += modifier.getFunctionCall(ShaderStage.VERTEX);
		}

		// As the position vertex attribute is mandatory and is a vec3, we can
		// manage
		// the gl_Position output case explicitly
		_vertexShaderSource += "\n\tgl_Position = va_Position;\n";

		_vertexShaderSource += "}\n";
	}

	/**
	 * \brief Builds the fragment shader source
	 */
	private void buildFragmentShaderSource()
	{
		_fragmentShaderSource = new String("#ifdef GL_ES\nprecision highp float;\n#endif\n");

		for (Iterator<Variable> it = _variableVect.iterator(); it.hasNext();)
		{
			Variable var = it.next();
			_fragmentShaderSource += var.getDeclarationInFragmentShader();
		}

		_fragmentShaderSource += "\n";

		for (Iterator<VariableModifier> it = _variableModifierVect.iterator(); it.hasNext();)
		{
			VariableModifier modifier = it.next();
			_fragmentShaderSource += modifier.getFunction(ShaderStage.FRAGMENT);
		}

		_fragmentShaderSource += "void main()\n{\n";

		for (Iterator<VariableModifier> it = _variableModifierVect.iterator(); it.hasNext();)
		{
			VariableModifier modifier = it.next();
			_fragmentShaderSource += modifier.getFunctionCall(ShaderStage.FRAGMENT);
		}

		_fragmentShaderSource += "\tgl_FragColor = vec4(biu_CustomColor.xyz, 1.);\n";

		_fragmentShaderSource += "}\n";
	}

	/**
	 * \brief Builds and compile the shader program
	 */
	protected void buildProgram()
	{
		// Build vertex and fragment shader source
		buildVertexShaderSource();
		buildFragmentShaderSource();

		//System.out.println("==== VERTEX\n" + _vertexShaderSource);
		// Try to compile vertex shader
		WebGLShader vertexShader = getShader(WebGLRenderingContext.VERTEX_SHADER, _vertexShaderSource);

		if (!_gl.getShaderParameterb(vertexShader, WebGLRenderingContext.COMPILE_STATUS))
		{
			String log = _gl.getShaderInfoLog(vertexShader);
			Window.alert("Cannot compile vertex shader : " + log);
			throw new RuntimeException("Cannot compile vertex shader");
		}
		
		//System.out.println("==== FRAGMENT\n" + _fragmentShaderSource);
		// Try to compile fragment shader
		WebGLShader fragmentShader = getShader(WebGLRenderingContext.FRAGMENT_SHADER, _fragmentShaderSource);

		if (!_gl.getShaderParameterb(fragmentShader, WebGLRenderingContext.COMPILE_STATUS))
		{
			String log = _gl.getShaderInfoLog(vertexShader);
			Window.alert("Cannot compile fragment shader : " + log);
			throw new RuntimeException("Cannot compile fragment shader");
		}
		
		//System.out.println("==== PROGRAM");
		// Finaly create the shader program object
		_program = _gl.createProgram();
		// Attach the compile vertex and fragment shader to it
		_gl.attachShader(_program, vertexShader);
		_gl.attachShader(_program, fragmentShader);

		// Bind vertex attributes
		int bindingCounter = 0;
		for (Iterator<Variable> it = _variableVect.iterator(); it.hasNext();)
		{
			Variable var = it.next();
			if (var.needBinding())
			{
				//System.out.println("binding : " + var.getBindingId() + " " + bindingCounter);
				_gl.bindAttribLocation(_program, bindingCounter, var.getBindingId());
				++bindingCounter;
			}
		}

		// And try to link it.
		_gl.linkProgram(_program);
		//System.out.println("=========");

		if (!_gl.getProgramParameterb(_program, WebGLRenderingContext.LINK_STATUS))
		{
			String log = _gl.getProgramInfoLog(_program);
			Window.alert("Could not initialize shaders : " + log);
			throw new RuntimeException("Could not initialise shaders");
		}
		else
		{
			// If the link has been done succesfuly
			// Get all the program's uniform variable location
			fillUniformVariablesLocation();
			
			// And mark the shader program as valid
			_programIsValid = true;
		}
	}

	/**
	 * \brief Adds a new vertex attribute variable to the shader
	 * @param[in] variableName Name of the variable to add
	 * @param[in] type Type of the variable to add
	 * @param[in] forwardMode Forward mode applied on the variable to add
	 */
	protected void addVertexAttributeVariable(String variableName, GLSLType type, VariableForwardMode forwardMode)
	{
		boolean ok = false;

		switch (forwardMode)
		{
		case VERTEX_ONLY:
		case VERTEX_AND_FRAGMENT:
			ok = true;
			break;

		case FRAGMENT_ONLY:
			ok = false;
			System.out.println("[etrl::DynamicShaderProgram::addVertexAttributeVariable] A VertexAttributeVariable must begin in Vertex shader, check the forward mode.");
			break;
		}

		if (ok)
		{
			Variable var = _variableMap.get(variableName);

			if (var == null)
			{
				Variable newVariable = new VertexAttributeVariable(variableName, type, forwardMode);
				_variableMap.put(variableName, newVariable);
				_variableVect.add(newVariable);
			}
			else
			{
				if (!var.getType().isEquals(type))
					System.out.println("[etrl::DynamicShaderProgram::addVertexAttributeVariable] Warning : the variable named " + variableName + " is already registered but with not the requested type.");
			}
		}
		else
			System.out.println("[etrl::DynamicShaderProgram::addVertexAttributeVariable] The VertexAttributeVariable " + variableName + " has not been added because an error occured.");
	}

	/**
	 * \brief Adds a new uniform variable to the shader
	 * @param[in] variableName Name of the variable to add
	 * @param[in] type Type of the variable to add
	 * @param[in] forwardMode Forward mode applied on the variable to add
	 * @param[in] elementCount Number of elements of the variable, if > 1 the variable is an array
	 * @param[in] uniformBinding The build-in uniform binding to apply
	 */
	protected void addUniformVariable(String variableName, GLSLType type, VariableForwardMode forwardMode, int elementCount, Binding uniformBinding)
	{
		// An uniform variable can appears in every shader kind separately.
		// We only need to check about the geometry shader activation.
		boolean ok = false;

		switch (forwardMode)
		{
		case VERTEX_ONLY:
		case VERTEX_AND_FRAGMENT:
		case FRAGMENT_ONLY:
			ok = true;
			break;
		}

		if (ok)
		{
			// Check if the variable name isnt already referenced into the variable map
			Variable var = _variableMap.get(variableName);

			// If not
			if (var == null)
			{
				// Create the uniform variable
				Variable newVariable = new UniformVariable(variableName, type, forwardMode, elementCount);
				
				// And reference it into variable map and vect
				_variableMap.put(variableName, newVariable);
				_variableVect.add(newVariable);
				
				// Then add the name to uniform location map, so the uniforma location will be
				// checked after the program build
				_uniformLocations.put(variableName, null);
				
				// And fill the uniform binding map
				addUniformBinding(variableName, type, uniformBinding, elementCount);
				
			}
			else
			{
				if (!var.getType().isEquals(type))
					System.out.println("[etrl::DynamicShaderProgram::addUniformVariable] Warning : the variable named " + variableName + " is already registered but with not the requested type.");
			}
		}
		else
			System.out.println("[etrl::DynamicShaderProgram::addUniformVariable] The UniformVariable " + variableName + " has not been added because an error occured.");
	}

	/**
	 * \brief Adds a new custom variable to the shader
	 * @param[in] variableName Name of the variable to add
	 * @param[in] type Type of the variable to add
	 * @param[in] forwardMode Forward mode applied on the variable to add
	 * @param[in] elementCount Number of elements of the variable, if > 1 the variable is an array
	 */
	protected void addCustomVariable(String variableName, GLSLType type, VariableForwardMode forwardMode, int elementCount)
	{
		// CustomVariables can have every forward mode
		Variable var = _variableMap.get(variableName);

		if (var == null)
		{
			Variable newVariable = new CustomVariable(variableName, type, forwardMode, elementCount);
			_variableMap.put(variableName, newVariable);
			_variableVect.add(newVariable);
		}
		else
		{
			if (!var.getType().isEquals(type))
				System.out.println("[etrl::DynamicShaderProgram::addCustomVariable] Warning : the variable named " + variableName + " is already registered but with not the requested type.");
		}
	}
	
	/**
	 * Uses a built-in uniform binding with the given variable
	 * @param variableName Name of the variable to use
	 * @param type Type of the variable to use
	 * @param uniformBinding Built-in uniform binding to use
	 * @param elementCount Element count of the variable
	 */
	private void addUniformBinding(String variableName, GLSLType type, Binding uniformBinding, int elementCount)
	{
		// If the binding is not defined, do nothing
		if(uniformBinding != Binding.BINDING_NONE)
		{
			// Now we have to check if the variable type is suitable for the desired binded uniform
			boolean readyToBind = false;
			
			switch(uniformBinding)
			{
				case BINDING_MODELVIEW_MATRIX :
					// ModelViewMatrix must be a 4x4 matrix
					if(type.getType() == GLSLType.Type.MAT4 && elementCount == 1)
						readyToBind = true;
				break;
				
				case BINDING_PROJECTION_MATRIX :
					// ProjectionMatrix must be a 4x4 matrix
					if(type.getType() == GLSLType.Type.MAT4 && elementCount == 1)
						readyToBind = true;
				break;
				
				case BINDING_NORMAL_MATRIX :
					// ModelViewMatrix must be a 3x3 matrix
					if(type.getType() == GLSLType.Type.MAT3 && elementCount == 1)
						readyToBind = true;
				break;
				
				case BINDING_TEXTURE_MATRIX0 :
				case BINDING_TEXTURE_MATRIX1 :
				case BINDING_TEXTURE_MATRIX2 :
				case BINDING_TEXTURE_MATRIX3 :					
				case BINDING_TEXTURE_MATRIX4 :
				case BINDING_TEXTURE_MATRIX5 :
				case BINDING_TEXTURE_MATRIX6 :
				case BINDING_TEXTURE_MATRIX7 :
					// Texture matrix of every texture unit must be 4x4 matrix
					if(type.getType() == GLSLType.Type.MAT4 && elementCount == 1)
						readyToBind = true;					
				break;
				
				case BINDING_TEXTURE0 :
				case BINDING_TEXTURE1 :
				case BINDING_TEXTURE2 :
				case BINDING_TEXTURE3 :
				case BINDING_TEXTURE4 :
				case BINDING_TEXTURE5 :
				case BINDING_TEXTURE6 :
				case BINDING_TEXTURE7 :
					// Textures must be Sampler2D 
					if(type.getType() == GLSLType.Type.SAMPLER2D && elementCount == 1)
						readyToBind = true;
				break;
				
				case BINDING_TEXTURE_COUNT :
					// Texture count must be a single int
					if(type.getType() == GLSLType.Type.INT && elementCount == 1)
						readyToBind = true;
				break;
				
				case BINDING_LIGHT_DATA :
					// Light data must be an array of MAXIMUM_NUMBER_OF_LIGHTS VEC4
					// Note that the engine limits itself to MAXIMUM_NUMBER_OF_LIGHTS light at same time. To increase the allowed number of lights,
					// we can just change the constant value to whatever but the number must be fixed for glsl..
					if(type.getType() == GLSLType.Type.VEC4 && elementCount == MAXIMUM_NUMBER_OF_LIGHTS)
						readyToBind = true;
				break;
					
				case BINDING_LIGHT_COLOR :
					// Idem as for light data
					// The light color array must be an array of MAXIMUM_NUMBER_OF_LIGHTS VEC3
					if(type.getType() == GLSLType.Type.VEC3 && elementCount == MAXIMUM_NUMBER_OF_LIGHTS)
						readyToBind = true;
				break;
				
				case BINDING_LIGHT_COUNT :
					// The light count must be a single int
					if(type.getType() == GLSLType.Type.INT && elementCount == 1)
						readyToBind = true;
				break;

				default :
					Window.alert("[DynamicShaderProgram::addUniformBinding] Warning, the given binding isnt managed, please contact developers");
				break;
			}
			
			// if the variable has correct type for the given binding, add it to map
			if(readyToBind) _bindings.put(uniformBinding, variableName);
		}
	}
	
	/**
	 * \brief Creates a new variable modifier with the given parameters
	 * @param[in] modifierName Variable modifier name
	 * @param[in] modifierSource Variable modifier source
	 * @param[in] stage Variable modifier stage
	 * @return New variable modifier if the requested name doesnt exist, else null
	 */
	public VariableModifier getNewVariableModifier(String modifierName, String modifierSource, ShaderStage stage)
	{
		VariableModifier modifier = _variableModifierMap.get(modifierName);

		if (modifier == null)
		{
			VariableModifier newVariableModifer = new VariableModifier(modifierName, modifierSource, stage);
			_variableModifierMap.put(modifierName, newVariableModifer);
			_variableModifierVect.add(newVariableModifer);

			return newVariableModifer;
		}
		else
		{
			System.out.println("[etrl::DynamicShaderProgram::getNewVariableModifier] A VariableModifier named " + modifierName + " already exists.");
			return null;
		}
	}

	/**
	 * \brief Gets a variable from its name
	 * @param[in] variableName Name of the variable to get
	 * @return Variable if it has been found, else null
	 */
	public Variable getVariable(String variableName)
	{
		Variable var = _variableMap.get(variableName);

		if (var != null)
			return var;
		else
		{
			System.out.println("[etrl::DynamicShaderProgram::getVariable] No registered variable named : " + variableName);
			return null;
		}
	}

	/**
	 * \brief Checks if the shader program object is valid
	 * @return true if the shader program object is valid, else false
	 */
	public boolean valid()
	{
		return _programIsValid;
	}

	/**
	 * \brief Build a shader object from its type and source
	 * @param[in] type Type of the shader to build
	 * @param[in] source Source of the shader to build
	 * @return GL shader object.
	 */
	private WebGLShader getShader(int type, String source)
	{
		WebGLShader shader = _gl.createShader(type);

		_gl.shaderSource(shader, source);
		_gl.compileShader(shader);

		if (!_gl.getShaderParameterb(shader, WebGLRenderingContext.COMPILE_STATUS))
		{
			throw new RuntimeException(_gl.getShaderInfoLog(shader));
		}

		return shader;
	}

	/**
	 * \brief Activates the shader program object
	 * @param[in] activateIt if true, activate the shader program, else deactivate it
	 */
	public void activate(boolean activateIt)
	{
		if (_programIsValid)
		{
			if (activateIt)
				_gl.useProgram(_program);
			else
				_gl.useProgram(null);
		}
		else
			System.out.println("[DynamicShaderProgram::activate] The shader program is not valid.");
	}

	/**
	 * \brief Gets the requested vertex attribute location 
	 * @param[in] attribName Name of the vertex attribute to get
	 * @return Vertex attribute location if found, else null
	 */
	public int getAttribLocation(String attribName)
	{
		return _gl.getAttribLocation(_program, attribName);
	}

	/**
	 * \brief Gets the requested uniform location 
	 * @param[in] uniformName Name of the uniform variable to get
	 * @return Uniform location if found, else null
	 */
	public WebGLUniformLocation getUniformLocation(String uniformName)
	{
		// Return the uniform location associated with the uniform name into the uniform location map.
		// If the uniformName is not found, null is returned
		WebGLUniformLocation location = _uniformLocations.get(uniformName);
		
		if(location == null)
			System.out.println("[DynamicShaderProgram::getUniformLocation] Requested uniform name (" + uniformName + ") doesnt exist ...");
			
		return location;
	}
	
	/**
	 * \brief Gets the location of all registered uniform variables and store them into the location map
	 */
	private void fillUniformVariablesLocation()
	{
		// Browse all the uniform tree map entries to fill them
		for (Entry<String, WebGLUniformLocation> entry : _uniformLocations.entrySet())
		{
			// Get the uniform location from the shader program and fill the entry with it
			_uniformLocations.put(entry.getKey(), _gl.getUniformLocation(_program, entry.getKey()));
		}	
	}
	
	/**
	 * \brief Gets uniform values from the render visitor and sends them to the shader program to binded variables
	 * @param visitor Current render visitor
	 */
	public void doUniformBinding(RenderVisitor visitor)
	{
		// Location used to send data to opengl
		WebGLUniformLocation location = null;
		
		// For each binding entry
		for ( Entry <Binding, String> entry : _bindings.entrySet())
		{
			// Send to opengl the correct value thanks to the visitor
			switch(entry.getKey())
			{
				case BINDING_MODELVIEW_MATRIX :
					location = getUniformLocation(entry.getValue());
					Matrix4f modelviewMatrix = visitor.getModelViewMatrix();
					_gl.uniformMatrix4fv(location, false, modelviewMatrix.asArray());
				break;
				
				case BINDING_PROJECTION_MATRIX :
					location = getUniformLocation(entry.getValue());
					Matrix4f projectionMatrix = visitor.getProjectionMatrix();
					_gl.uniformMatrix4fv(location, false, projectionMatrix.asArray());					
				break;
				
				case BINDING_NORMAL_MATRIX :
					location = getUniformLocation(entry.getValue());
					Matrix3f normalMatrix = visitor.getNormalMatrix();
					_gl.uniformMatrix3fv(location, false, normalMatrix.asArray());					
				break;	
				
				case BINDING_TEXTURE_MATRIX0 :
					// Check activated textures count to not do useless opengl calls.
					if(visitor.getTextureCount() > 0)
					{
						location = getUniformLocation(entry.getValue());
						Matrix4f textureMatrix0 = visitor.getTextureMatrix(0);
						_gl.uniformMatrix4fv(location, false, textureMatrix0.asArray());
					}
				break;
				
				case BINDING_TEXTURE_MATRIX1 :
					// Check activated textures count to not do useless opengl calls.
					if(visitor.getTextureCount() > 1)
					{
						location = getUniformLocation(entry.getValue());
						Matrix4f textureMatrix1 = visitor.getTextureMatrix(1);
						_gl.uniformMatrix4fv(location, false, textureMatrix1.asArray());
					}
				break;
				
				case BINDING_TEXTURE_MATRIX2 :
					// Check activated textures count to not do useless opengl calls.
					if(visitor.getTextureCount() > 2)
					{
						location = getUniformLocation(entry.getValue());
						Matrix4f textureMatrix2 = visitor.getTextureMatrix(2);
						_gl.uniformMatrix4fv(location, false, textureMatrix2.asArray());
					}
				break;
				
				case BINDING_TEXTURE_MATRIX3 :
					// Check activated textures count to not do useless opengl calls.
					if(visitor.getTextureCount() > 3)
					{
						location = getUniformLocation(entry.getValue());
						Matrix4f textureMatrix3 = visitor.getTextureMatrix(3);
						_gl.uniformMatrix4fv(location, false, textureMatrix3.asArray());
					}
				break;
				
				case BINDING_TEXTURE_MATRIX4 :
					// Check activated textures count to not do useless opengl calls.
					if(visitor.getTextureCount() > 4)
					{
						location = getUniformLocation(entry.getValue());
						Matrix4f textureMatrix4 = visitor.getTextureMatrix(4);
						_gl.uniformMatrix4fv(location, false, textureMatrix4.asArray());
					}
				break;
				
				case BINDING_TEXTURE_MATRIX5 :
					// Check activated textures count to not do useless opengl calls.
					if(visitor.getTextureCount() > 5)
					{
						location = getUniformLocation(entry.getValue());
						Matrix4f textureMatrix5 = visitor.getTextureMatrix(5);
						_gl.uniformMatrix4fv(location, false, textureMatrix5.asArray());
					}
				break;
				
				case BINDING_TEXTURE_MATRIX6 :
					// Check activated textures count to not do useless opengl calls.
					if(visitor.getTextureCount() > 6)
					{
						location = getUniformLocation(entry.getValue());
						Matrix4f textureMatrix6 = visitor.getTextureMatrix(6);
						_gl.uniformMatrix4fv(location, false, textureMatrix6.asArray());
					}
				break;
				
				case BINDING_TEXTURE_MATRIX7 :
					// Check activated textures count to not do useless opengl calls.
					if(visitor.getTextureCount() > 7)
					{
						location = getUniformLocation(entry.getValue());
						Matrix4f textureMatrix7 = visitor.getTextureMatrix(7);
						_gl.uniformMatrix4fv(location, false, textureMatrix7.asArray());
					}
				break;
				
				case BINDING_TEXTURE_COUNT :
					location = getUniformLocation(entry.getValue());
					int textureCount = visitor.getTextureCount();
					_gl.uniform1i(location, textureCount);
				break;
				
				case BINDING_TEXTURE0 :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, 0);
				break;
				
				case BINDING_TEXTURE1 :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, 1);
				break;
						
				case BINDING_TEXTURE2 :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, 2);
				break;
				
				case BINDING_TEXTURE3 :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, 3);
				break;
				
				case BINDING_TEXTURE4 :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, 4);
				break;
				
				case BINDING_TEXTURE5 :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, 5);
				break;
				
				case BINDING_TEXTURE6 :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, 6);
				break;
				
				case BINDING_TEXTURE7 :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, 7);
				break;
				
				case BINDING_LIGHT_DATA :
					// If there is no light activated, avoid useless call
					if(visitor.getLightCount() > 0)
					{
						location = getUniformLocation(entry.getValue());
						_gl.uniform4fv(location, visitor.getLightData());
					}
				break;
					
				case BINDING_LIGHT_COLOR :
					// If there is no light activated, avoid useless call
					if(visitor.getLightCount() > 0)
					{
						location = getUniformLocation(entry.getValue());
						_gl.uniform3fv(location, visitor.getLightColor());
					}
				break;
				
				case BINDING_LIGHT_COUNT :
					location = getUniformLocation(entry.getValue());
					_gl.uniform1i(location, visitor.getLightCount());
				break;
			}
		}
	}
}
